/* Copyright Thanh-Trung Pham, JAIST, 2012 */

package jp.ac.jaist.input;

import java.util.Arrays;
import jp.ac.jaist.BranchingStructureType;
import jp.ac.jaist.BranchingStructureType.IfPart;
import jp.ac.jaist.CallingActivityType;
import jp.ac.jaist.ComponentConnectorType.For;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import jp.ac.jaist.CallPropagationType;
import jp.ac.jaist.ComponentConnectorType;
import jp.ac.jaist.ComponentInstanceType;
import jp.ac.jaist.ComponentType;
import jp.ac.jaist.ComponentType.RequiredService;
import jp.ac.jaist.InternalActivityType;
import jp.ac.jaist.LoopingStructureType;
import jp.ac.jaist.ParallelStructureType;
import jp.ac.jaist.PrimaryBackupFaultToleranceStructureType;
import jp.ac.jaist.ServiceImplementationType;
import jp.ac.jaist.ServiceType;
import jp.ac.jaist.SystemArchitectureType;
import jp.ac.jaist.UsageProfile;
import jp.ac.jaist.UsageProfile.Average;
import jp.ac.jaist.UsageProfile.Probability;
import jp.ac.jaist.UserInterface;
import jp.ac.jaist.message.RMInputModelException;
import jp.ac.jaist.message.RMMessageResource;
import org.apache.commons.lang3.StringUtils;
import org.apache.xmlbeans.XmlError;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;

/**
 *
 * @author Thanh-Trung Pham
 */
public class RMInputModelManager {

    private static final String RM_SEMICOLON = "; ";
    private static final String RM_DOT = ".";
    private String m_strInputModelFilename;
    //private SystemReliabilityModelingDocument m_oDocument;
    private XmlObject m_oXmlObject;
    private final static String m_namespaceDeclaration = "declare namespace tns='jaist.ac.jp';";
    private static RMInputModelManager m_oManager;
    private List<XmlError> m_oXMLErrors;

    /**
     * The constructor.
     */
    private RMInputModelManager() {
    }

    /**
     * Get the only instance.
     * @return the only instance.
     */
    public static synchronized RMInputModelManager getInstance() {
        if (m_oManager == null) {
            m_oManager = new RMInputModelManager();
        }

        return m_oManager;
    }

    /**
     * Set the input model file name.
     * @param inputModelFilename
     * @throws RMInputModelException
     */
    public void setInputModelFilename(String inputModelFilename) throws RMInputModelException {
        m_strInputModelFilename = inputModelFilename;

        initialize();
    }

    /**
     * Get the input model filename.
     * @return the input model filename.
     */
    public String getInputModelFilename() {
        return m_strInputModelFilename;
    }

    /**
     * Initialize.
     * @throws RMInputModelException
     */
    private void initialize() throws RMInputModelException {
        File oFile = new File(m_strInputModelFilename);
        m_oXMLErrors = new ArrayList<XmlError>();

        if (!oFile.exists()) {
            throw new RMInputModelException(RMMessageResource.IMM_NON_EXISTING_INPUT_MODEL_FILE, m_strInputModelFilename, false);
        }

        try {
            XmlOptions oXMLOptions = new XmlOptions();
            oXMLOptions.setLoadLineNumbers(XmlOptions.LOAD_LINE_NUMBERS);

            //m_oDocument = SystemReliabilityModelingDocument.Factory.parse(oFile, oXMLOptions);
            
            m_oXmlObject = XmlObject.Factory.parse(oFile, oXMLOptions);

            validate();
        } catch (XmlException oXmlException) {
            throw new RMInputModelException(RMMessageResource.IMM_UNEXPECTED_EXCEPTION, oXmlException, false);
        } catch (IOException oIOException) {
            throw new RMInputModelException(RMMessageResource.IMM_UNEXPECTED_EXCEPTION, oIOException, false);
        }
    }

    /**
     * Validate.
     * @throws RMInputModelException
     */
    private void validate() throws RMInputModelException {
        XmlOptions oValidateOptions = new XmlOptions();
        oValidateOptions.setLoadLineNumbers(XmlOptions.LOAD_LINE_NUMBERS);

        oValidateOptions.setErrorListener(m_oXMLErrors);

        //boolean isValid = m_oDocument.validate(oValidateOptions);
        boolean isValid = m_oXmlObject.validate(oValidateOptions);

        if (!isValid) {
            //m_oDocument = null;
            m_oXmlObject = null;

            throw new RMInputModelException(RMMessageResource.IMM_INVALID_INPUT_MODEL, false);
        }

        verifyServices();
        verifyComponents();
        verifySystemArchitectures();
        verifyUserInterfaces();
        verifyUsageProfiles();
    }

    /**
     * Verify the given service implementation.
     * @param serviceImplementation the given service implementation.
     * @param componentName the component name.
     * @param requiredServiceNames the list of required service names in the given component.
     * @return a list of called service names of calling activities.
     * @throws RMInputModelException if the given service implementation is invalid.
     */
    private List<String> verifyServiceImplementation(ServiceImplementationType serviceImplementation, String componentName, List<String> requiredServiceNames) throws RMInputModelException {
        String strForService = serviceImplementation.getForService();
        List<String> oListOfIANames = new ArrayList<String>();

        List<String> oListOfCalledServiceNames = new ArrayList<String>();

        LinkedList<CallPropagationType> oTODOs = new LinkedList<CallPropagationType>();
        oTODOs.add(serviceImplementation);
        while (!oTODOs.isEmpty()) {
            CallPropagationType oTODO = oTODOs.removeFirst();
            if (oTODO.isSetInternalActivity()) {
                InternalActivityType oIA = oTODO.getInternalActivity();
                if ((oIA.getSfp() + oIA.getPfp() > 1.0) || (oIA.getSep() + oIA.getNmep() > 1.0)) {
                    throw new RMInputModelException(RMMessageResource.IMM_INVALID_INTERNAL_ACTIVITY_IN_SERVICE_IMPLEMENTATION_IN_COMPONENT, strForService, componentName, false);
                }
                String strIAName = oIA.getName();
                if (oListOfIANames.contains(strIAName)) {
                    throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_INTERNAL_ACTIVITY_NAME_IN_SERVICE_IMPLEMENTATION_IN_COMPONENT, strIAName, strForService, componentName, false);
                }
                oListOfIANames.add(strIAName);
            } else if (oTODO.isSetCallingActivity()) {
                String strCalledService = oTODO.getCallingActivity().getCalledService();
                if (!requiredServiceNames.contains(strCalledService)) {
                    throw new RMInputModelException(RMMessageResource.IMM_INVALID_CALLING_ACTIVITY_IN_SERVICE_IMPLEMENTATION_IN_COMPONENT, strCalledService, strForService, componentName, false);
                }

                oListOfCalledServiceNames.add(strCalledService);
            } else if (oTODO.isSetBranchingStructure()) {
                BranchingStructureType oBranchingStructure = oTODO.getBranchingStructure();
                oTODOs.addAll(Arrays.asList(oBranchingStructure.getIfPartArray()));
                oTODOs.add(oBranchingStructure.getElsePart());
            } else if (oTODO.isSetLoopingStructure()) {
                oTODOs.add(oTODO.getLoopingStructure().getLoopingPart());
            } else if (oTODO.isSetParallelStructure()) {
                oTODOs.addAll(Arrays.asList(oTODO.getParallelStructure().getParallelPartArray()));
            } else if (oTODO.isSetPrimaryBackupFaultToleranceStructure()) {
                oTODOs.add(oTODO.getPrimaryBackupFaultToleranceStructure().getPrimaryPart());
                oTODOs.addAll(Arrays.asList(oTODO.getPrimaryBackupFaultToleranceStructure().getBackupPartArray()));
            } else if (oTODO.isSetSequentialStructure()) {
                oTODOs.addAll(Arrays.asList(oTODO.getSequentialStructure().getSequentialPartArray()));
            }
        }

        return oListOfCalledServiceNames;
    }

    /**
     * Verify system architectures of the input model.
     * @throws RMInputModelException if system architectures are invalid.
     */
    private void verifySystemArchitectures() throws RMInputModelException {
        SystemArchitectureType[] oSAs = getSystemArchitectures();
        List<String> oListOfSANames = new ArrayList<String>();

        for (SystemArchitectureType oSA : oSAs) {
            String strSAName = oSA.getName();

            if (oListOfSANames.contains(strSAName)) {
                throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_SYSTEM_ARCHITECTURE, strSAName, false);
            }

            HashMap<String, String> oComponentInstanceInfo = verifyComponentInstances(oSA);
            verifyComponentConnectors(oSA, oComponentInstanceInfo);

            oListOfSANames.add(strSAName);
        }
    }

    /**
     * Verify usage profiles of the input mode.
     * @throws RMInputModelException if usage profiles are invalid.
     */
    private void verifyUsageProfiles() throws RMInputModelException {
        UsageProfile[] oUPs = getUsageProfiles();
        List<String> oListOfUPForUINames = new ArrayList<String>();

        for (UsageProfile oUP : oUPs) {
            String strUPForUIName = oUP.getForUserInterface();

            if (oListOfUPForUINames.contains(strUPForUIName)) {
                throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_USAGE_PROFILE, strUPForUIName, false);
            }

            UserInterface oUserInterface = getUserInterfaceByName(strUPForUIName);
            if (oUserInterface == null) {
                throw new RMInputModelException(RMMessageResource.IMM_USAGE_PROFILE_REFERRING_TO_NON_EXISTING_USER_INTERFACE, strUPForUIName, false);
            }

            verifyAverages(oUP);
            verifyProbabilities(oUP);

            oListOfUPForUINames.add(strUPForUIName);
        }
    }

    /**
     * Verify branching conditions, looping counts and recursive calls for a user interface.
     * @param userInterface the user interface.
     * @throws RMInputModelException if there are recursive calls, missing/invalid looping counts and missing/invalid branching conditions.
     */
    private void verifyBranchingConditionsAndLoopingCountsAndRecursiveCalls(UserInterface userInterface) throws RMInputModelException {
        String strUIComponentInstanceName = userInterface.getFrom().getFromComponentInstance();
        String strUIProvidedServiceName = userInterface.getFrom().getFromProvidedService();

        List<String> oTrackedFullProvidedServices = new ArrayList<String>();

        verifyBranchingConditionsAndLoopingCountsAndRecursiveCalls(userInterface, strUIComponentInstanceName, strUIProvidedServiceName, oTrackedFullProvidedServices);
    }

    /**
     * Verify branching conditions, looping counts and recursive calls for a provided service of a component instance.
     * @param userInterface the user interface.
     * @param componentInstanceName the component instance name.
     * @param providedServiceName the provided service name.
     * @param trackedFullProvidedServices the list of tracked full provided services.
     * @throws RMInputModelException if there are recursive calls, missing/invalid looping counts and missing/invalid branching conditions.
     */
    private void verifyBranchingConditionsAndLoopingCountsAndRecursiveCalls(UserInterface userInterface, String componentInstanceName, String providedServiceName, List<String> trackedFullProvidedServices) throws RMInputModelException {
        String strFullProvidedServiceName = componentInstanceName + RM_DOT + providedServiceName;
        if (trackedFullProvidedServices.contains(strFullProvidedServiceName)) {
            throw new RMInputModelException(RMMessageResource.IMM_USER_INTERFACE_CONTAINING_RECURSIVE_CALLS, userInterface.getName(), providedServiceName, componentInstanceName, false);
        }

        trackedFullProvidedServices.add(strFullProvidedServiceName);

        ComponentInstanceType oComponentInstance = getComponentInstance(userInterface.getForSystemArchitecture(), componentInstanceName);
        String strComponentName = oComponentInstance.getForComponent();
        ServiceImplementationType oServiceImplementation = getServiceImplementation(strComponentName, providedServiceName);

        verifyBranchingConditionsAndLoopingCounts(userInterface, componentInstanceName, oServiceImplementation, trackedFullProvidedServices);

        trackedFullProvidedServices.remove(strFullProvidedServiceName);
    }

    /**
     * Verify branching conditions and looping counts for a call propagation.
     * @param userInterface the user interface.
     * @param componentInstanceName the component instance name.
     * @param callPropagation the call propagation.
     * @param trackedFullProvidedServices the list of tracked full provided services.e
     * @throws RMInputModelException if there are missing/invalid looping counts and missing/invalid branching conditions.
     */
    private void verifyBranchingConditionsAndLoopingCounts(UserInterface userInterface, String componentInstanceName, CallPropagationType callPropagation, List<String> trackedFullProvidedServices) throws RMInputModelException {
        if (callPropagation.isSetBranchingStructure()) {
            BranchingStructureType oBranchingStructure = callPropagation.getBranchingStructure();
            IfPart[] oIfParts = oBranchingStructure.getIfPartArray();

            double dProbabilityOfAllIfBranches = 0.0;

            String strConditionLabels = StringUtils.EMPTY;

            for (IfPart oIfPart : oIfParts) {
                verifyBranchingConditionsAndLoopingCounts(userInterface, componentInstanceName, oIfPart, trackedFullProvidedServices);

                String strConditionLabel = oIfPart.getBranchingCondition();

                Probability oUserProfileProbability = getUsageProfileProbability(userInterface.getName(), strConditionLabel);
                if (oUserProfileProbability == null) {
                    throw new RMInputModelException(RMMessageResource.IMM_USER_INTERFACE_MISSING_USAGE_PROFILE_PROBABILITY, strConditionLabel, userInterface.getName(), false);
                }

                dProbabilityOfAllIfBranches += oUserProfileProbability.getValue();
                if (StringUtils.isEmpty(strConditionLabels)) {
                    strConditionLabels += strConditionLabel;
                } else {
                    strConditionLabels += RM_SEMICOLON + strConditionLabel;
                }
            }

            if (dProbabilityOfAllIfBranches > 1.0) {
                throw new RMInputModelException(RMMessageResource.IMM_USER_INTERFACE_INVALID_USAGE_PROFILE_PROBABILITIES, userInterface.getName(), strConditionLabels, false);
            }

            verifyBranchingConditionsAndLoopingCounts(userInterface, componentInstanceName, oBranchingStructure.getElsePart(), trackedFullProvidedServices);
        } else if (callPropagation.isSetLoopingStructure()) {
            LoopingStructureType oLoopingStructure = callPropagation.getLoopingStructure();

            verifyBranchingConditionsAndLoopingCounts(userInterface, componentInstanceName, oLoopingStructure.getLoopingPart(), trackedFullProvidedServices);

            String strLoopCount = oLoopingStructure.getLoopCount();
            if (getUsageProfileAverage(userInterface.getName(), strLoopCount) == null) {
                throw new RMInputModelException(RMMessageResource.IMM_USER_INTERFACE_MISSING_USAGE_PROFILE_AVERAGE, strLoopCount, userInterface.getName(), false);
            }
        } else if (callPropagation.isSetParallelStructure()) {
            ParallelStructureType oParallelStructure = callPropagation.getParallelStructure();
            CallPropagationType[] oParallelParts = oParallelStructure.getParallelPartArray();
            for (CallPropagationType oParallelPart : oParallelParts) {
                verifyBranchingConditionsAndLoopingCounts(userInterface, componentInstanceName, oParallelPart, trackedFullProvidedServices);
            }
        } else if (callPropagation.isSetPrimaryBackupFaultToleranceStructure()) {
            PrimaryBackupFaultToleranceStructureType oPrimaryBackupFaultToleranceStructure = callPropagation.getPrimaryBackupFaultToleranceStructure();
            verifyBranchingConditionsAndLoopingCounts(userInterface, componentInstanceName, oPrimaryBackupFaultToleranceStructure.getPrimaryPart(), trackedFullProvidedServices);

            CallPropagationType[] oBackupParts = oPrimaryBackupFaultToleranceStructure.getBackupPartArray();
            for (CallPropagationType oBackupPart : oBackupParts) {
                verifyBranchingConditionsAndLoopingCounts(userInterface, componentInstanceName, oBackupPart, trackedFullProvidedServices);
            }
        } else if (callPropagation.isSetCallingActivity()) {
            CallingActivityType oCallingActivity = callPropagation.getCallingActivity();
            String calledServiceName = oCallingActivity.getCalledService();

            ComponentConnectorType oComponentConnector = getComponentConnectorFor(userInterface.getForSystemArchitecture(), componentInstanceName, calledServiceName);
            if (oComponentConnector == null) {
                throw new RMInputModelException(RMMessageResource.IMM_USER_INTERFACE_MISSING_COMPONENT_CONNECTOR, componentInstanceName + RM_DOT + calledServiceName, userInterface.getName(), false);
            }

            String strFromComponentInstance = oComponentConnector.getFrom().getFromComponentInstance();
            String strFromProvidedServiceName = oComponentConnector.getFrom().getFromProvidedService();

            verifyBranchingConditionsAndLoopingCountsAndRecursiveCalls(userInterface, strFromComponentInstance, strFromProvidedServiceName, trackedFullProvidedServices);

        } else if (callPropagation.isSetSequentialStructure()) {
            CallPropagationType[] oSequentialParts = callPropagation.getSequentialStructure().getSequentialPartArray();
            for (CallPropagationType oSequentialPart : oSequentialParts) {
                verifyBranchingConditionsAndLoopingCounts(userInterface, componentInstanceName, oSequentialPart, trackedFullProvidedServices);
            }
        }
    }

    /**
     * Verify component instances in a given system architecture.
     * @param systemArchitecture the given system architecture.
     * @return a hash map of component instance name-component name mappings.
     * @throws RMInputModelException if component instances in the given system architecture are invalid.
     */
    private HashMap<String, String> verifyComponentInstances(SystemArchitectureType systemArchitecture) throws RMInputModelException {
        String strSystemArchitectureName = systemArchitecture.getName();

        HashMap<String, String> oListOfComponentInstanceInfo = new HashMap<String, String>();

        ComponentInstanceType[] oComponentInstances = systemArchitecture.getComponentInstanceArray();
        for (ComponentInstanceType oComponentInstance : oComponentInstances) {
            String strComponentInstanceName = oComponentInstance.getName();

            if (oListOfComponentInstanceInfo.containsKey(strComponentInstanceName)) {
                throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_COMPONENT_INSTANCE_IN_SYSTEM_ARCHITECTURE, strComponentInstanceName, strSystemArchitectureName, false);
            }

            String strComponentName = oComponentInstance.getForComponent();
            ComponentType oComponent = getComponentByName(strComponentName);
            if (oComponent == null) {
                throw new RMInputModelException(RMMessageResource.IMM_NOT_DECLARED_COMPONENT_IN_SYSTEM_ARCHITECTURE, strComponentInstanceName, strComponentName, strSystemArchitectureName, false);
            }

            oListOfComponentInstanceInfo.put(strComponentInstanceName, strComponentName);
        }

        return oListOfComponentInstanceInfo;
    }

    /**
     * Verify component connectors in a given system architecture.
     * @param systemArchitecture the given system architecture.
     * @param a hash map of component instance name-component name mappings.
     * @throws RMInputModelException if the component connectors in the given system architecture are invalid.
     */
    private void verifyComponentConnectors(SystemArchitectureType systemArchitecture, HashMap<String, String> componentInstanceInfo) throws RMInputModelException {
        String strSystemArchitectureName = systemArchitecture.getName();

        List<String> oListOfComponentComponentConnectorNames = new ArrayList<String>();

        ComponentConnectorType[] oComponentConnectors = systemArchitecture.getComponentConnectorArray();
        for (ComponentConnectorType oComponentConnector : oComponentConnectors) {
            String strForComponentInstanceName = oComponentConnector.getFor().getForComponentInstance();
            String strForRequiredServiceName = oComponentConnector.getFor().getForRequiredService();

            String strComponentConnectorName = strForComponentInstanceName + RM_DOT + strForRequiredServiceName;
            if (oListOfComponentComponentConnectorNames.contains(strComponentConnectorName)) {
                throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_COMPONENT_CONNECTOR_IN_SYSTEM_ARCHITECTURE, strForRequiredServiceName, strForComponentInstanceName, strSystemArchitectureName, false);
            }

            if (!componentInstanceInfo.containsKey(strForComponentInstanceName)) {
                throw new RMInputModelException(RMMessageResource.IMM_NOT_DECLARED_COMPONENT_INSTANCE_IN_SYSTEM_ARCHITECTURE, strForComponentInstanceName, strSystemArchitectureName, false);
            }

            String strComponentName1 = componentInstanceInfo.get(strForComponentInstanceName);
            if (getRequiredService(strComponentName1, strForRequiredServiceName) == null) {
                throw new RMInputModelException(RMMessageResource.IMM_NOT_EXISTING_REQUIRED_SERVICE_IN_COMPONENT_CONNECTOR_IN_SYSTEM_ARCHITECTURE, strForRequiredServiceName, strForComponentInstanceName, strSystemArchitectureName, false);
            }

            String strFromComponentInstanceName = oComponentConnector.getFrom().getFromComponentInstance();
            if (!componentInstanceInfo.containsKey(strFromComponentInstanceName)) {
                throw new RMInputModelException(RMMessageResource.IMM_NOT_DECLARED_COMPONENT_INSTANCE_IN_SYSTEM_ARCHITECTURE, strFromComponentInstanceName, strSystemArchitectureName, false);
            }

            String strFromProvidedServiceName = oComponentConnector.getFrom().getFromProvidedService();
            String strComponentName2 = componentInstanceInfo.get(strFromComponentInstanceName);
            if (getProvidedService(strComponentName2, strFromProvidedServiceName) == null) {
                throw new RMInputModelException(RMMessageResource.IMM_NOT_EXISTING_PROVIDED_SERVICE_IN_COMPONENT_CONNECTOR_IN_SYSTEM_ARCHITECTURE, strFromProvidedServiceName, strFromComponentInstanceName, strSystemArchitectureName, false);
            }

            oListOfComponentComponentConnectorNames.add(strComponentConnectorName);
        }
    }

    /**
     * Get a required service from a component by from service name.
     * @param componentName the given component.
     * @param fromServiceName the from service name.
     * @return the required service.
     */
    public RequiredService getRequiredService(String componentName, String fromServiceName) {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:ComponentReliabilitySpecification/tns:Component[@name='" + componentName + "']/tns:RequiredService[@fromService='" + fromServiceName + "']";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (RequiredService) oXmlObjects[0];
    }

    /**
     * Get a provided service from a component by from service name.
     * @param componentName the given component.
     * @param forServiceName the from service name.
     * @return the provided service.
     */
    public ComponentType.ProvidedService getProvidedService(String componentName, String forServiceName) {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:ComponentReliabilitySpecification/tns:Component[@name='" + componentName + "']/tns:ProvidedService[@forService='" + forServiceName + "']";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (ComponentType.ProvidedService) oXmlObjects[0];
    }

    /**
     * Verify for averages in a given usage profile.
     * @param usageProfile the given usage profile.
     * @throws RMInputModelException the averages in the given usage profile are invalid.
     */
    private void verifyAverages(UsageProfile usageProfile) throws RMInputModelException {
        String strForUserInterface = usageProfile.getForUserInterface();
        List<String> oListOfAverageLabels = new ArrayList<String>();

        Average[] oAverages = usageProfile.getAverageArray();
        for (Average oAverage : oAverages) {
            String strAverageLabel = oAverage.getLabel();

            if (oListOfAverageLabels.contains(strAverageLabel)) {
                throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_AVERAGE_IN_USAGE_PROFILE, strAverageLabel, strForUserInterface, false);
            }

            oListOfAverageLabels.add(strAverageLabel);
        }
    }

    /**
     * Verify for probabilities in a given usage profile.
     * @param usageProfile the given usage profile.
     * @throws RMInputModelException the probabilities in the given usage profile are invalid.
     */
    private void verifyProbabilities(UsageProfile usageProfile) throws RMInputModelException {
        String strForUserInterface = usageProfile.getForUserInterface();

        List<String> oListOfProbabilityLabels = new ArrayList<String>();

        Probability[] oProbabilities = usageProfile.getProbabilityArray();
        for (Probability oProbability : oProbabilities) {
            String strProbabilityLabel = oProbability.getLabel();

            if (oListOfProbabilityLabels.contains(strProbabilityLabel)) {
                throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_PROBABILITY_IN_USAGE_PROFILE, strProbabilityLabel, strForUserInterface, false);
            }

            oListOfProbabilityLabels.add(strProbabilityLabel);
        }
    }

    /**
     * Verify services of the input model.
     * @throws RMInputModelException if the services of the input model are invalid.
     */
    private void verifyServices() throws RMInputModelException {
        ServiceType[] oServices = getServices();
        List<String> oListOfServiceNames = new ArrayList<String>();

        for (ServiceType oService : oServices) {
            String strServiceName = oService.getName();

            if (oListOfServiceNames.contains(strServiceName)) {
                throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_SERVICE, strServiceName, false);
            }

            oListOfServiceNames.add(strServiceName);
        }
    }

    /**
     * Verify components of the input model.
     * @throws RMInputModelException if the components of the input model are invalid.
     */
    private void verifyComponents() throws RMInputModelException {
        ComponentType[] oComponents = getComponents();
        List<String> oListOfCompnents = new ArrayList<String>();

        for (ComponentType oComponent : oComponents) {
            String strComponentName = oComponent.getName();

            if (oListOfCompnents.contains(strComponentName)) {
                throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_COMPONENT, strComponentName, false);
            }

            List<String> oRequiredServiceNames = verifyRequiredServices(oComponent);
            List<String> oProvidedServiceNames = verifyProvidedServices(oComponent);
            verifyServiceImplementations(oComponent, oProvidedServiceNames, oRequiredServiceNames);

            oListOfCompnents.add(strComponentName);
        }
    }

    /**
     * Verify service implementations in a given component.
     * @param component the given component.
     * @throws RMInputModelException if the service implements in the given component are invalid.
     */
    private void verifyServiceImplementations(ComponentType component, List<String> providedServiceNames, List<String> requiredServiceNames) throws RMInputModelException {
        String strComponentName = component.getName();

        ServiceImplementationType[] oSIs = component.getServiceImplementationArray();
        List<String> oListOfSINames = new ArrayList<String>();
        List<String> oListOfCalledServiceNames = new ArrayList<String>();

        for (ServiceImplementationType oSI : oSIs) {
            String strForService = oSI.getForService();

            if (oListOfSINames.contains(strForService)) {
                throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_SERVICE_IMPLEMENTATION_IN_COMPONENT, strForService, strComponentName, false);
            }

            if (!providedServiceNames.contains(strForService)) {
                throw new RMInputModelException(RMMessageResource.IMM_NOT_FOR_PROVIDED_SERVICE_IMPLEMENTATION_IN_COMPONENT, strForService, strComponentName, false);
            }

            oListOfCalledServiceNames.addAll(verifyServiceImplementation(oSI, strComponentName, requiredServiceNames));
            oListOfSINames.add(strForService);
        }

        for (String strProvidedServiceName : providedServiceNames) {
            if (!oListOfSINames.contains(strProvidedServiceName)) {
                throw new RMInputModelException(RMMessageResource.IMM_MISSING_SERVICE_IMPLEMENTATION_IN_COMPONENT, strProvidedServiceName, strComponentName, false);
            }
        }

        for (String strRequiredServiceName : requiredServiceNames) {
            if (!oListOfCalledServiceNames.contains(strRequiredServiceName)) {
                throw new RMInputModelException(RMMessageResource.IMM_REDUNDANT_REQUIRED_SERVICE_IN_COMPONENT, strRequiredServiceName, strComponentName, false);
            }
        }
    }

    /**
     * Verify required services in a given component.
     * @param component the given component.
     * @return a list of required service names.
     * @throws RMInputModelException if the required services in the given component are invalid.
     */
    private List<String> verifyRequiredServices(ComponentType component) throws RMInputModelException {
        String strComponentName = component.getName();

        RequiredService[] oRequiredServices = component.getRequiredServiceArray();
        List<String> oListOfRequiredServiceNames = new ArrayList<String>();

        for (RequiredService requiredService : oRequiredServices) {
            String strRequiredServiceName = requiredService.getFromService();

            if (oListOfRequiredServiceNames.contains(strRequiredServiceName)) {
                throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_REQUIRED_SERVICE_IN_COMPONENT, strRequiredServiceName, strComponentName, false);
            }

            ServiceType oService = getServiceByName(strRequiredServiceName);
            if (oService == null) {
                throw new RMInputModelException(RMMessageResource.IMM_NOT_DECLARED_REQUIRED_SERVICE_IN_COMPONENT, strRequiredServiceName, strComponentName, false);
            }

            oListOfRequiredServiceNames.add(strRequiredServiceName);
        }

        return oListOfRequiredServiceNames;
    }

    /**
     * Verify provided services in a given component.
     * @param component the given component.
     * @return a list of provided service names.
     * @throws RMInputModelException if the provided services in the given component are invalid.
     */
    private List<String> verifyProvidedServices(ComponentType component) throws RMInputModelException {
        String strComponentName = component.getName();

        ComponentType.ProvidedService[] oProvidedServices = component.getProvidedServiceArray();
        List<String> oListOfProvidedServiceNames = new ArrayList<String>();

        for (ComponentType.ProvidedService providedService : oProvidedServices) {
            String strProvidedServiceName = providedService.getForService();

            if (oListOfProvidedServiceNames.contains(strProvidedServiceName)) {
                throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_PROVIDED_SERVICE_IN_COMPONENT, strProvidedServiceName, strComponentName, false);
            }

            ServiceType oService = getServiceByName(strProvidedServiceName);
            if (oService == null) {
                throw new RMInputModelException(RMMessageResource.IMM_NOT_DECLARED_PROVIDED_SERVICE_IN_COMPONENT, strProvidedServiceName, strComponentName, false);
            }

            oListOfProvidedServiceNames.add(strProvidedServiceName);
        }

        return oListOfProvidedServiceNames;
    }

    /**
     * Get a service by its name.
     * @param serviceName the given service name.
     * @return the service with the given name.
     */
    public ServiceType getServiceByName(String serviceName) {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:ComponentReliabilitySpecification/tns:Service[@name='" + serviceName + "']";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (ServiceType) oXmlObjects[0];
    }

    /**
     * Get a component by its name.
     * @param componentName the given component name.
     * @return the component with the given name.
     */
    public ComponentType getComponentByName(String componentName) {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:ComponentReliabilitySpecification/tns:Component[@name='" + componentName + "']";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (ComponentType) oXmlObjects[0];
    }

    /**
     * Verify user interfaces of the input mode.
     * @throws RMInputModelException if the user interfaces of the input model are invalid.
     */
    private void verifyUserInterfaces() throws RMInputModelException {
        UserInterface[] oUserInterfaces = getUserInterfaces();
        List<String> oListOfUserInterfaceNames = new ArrayList<String>();

        for (UserInterface oUserInterface : oUserInterfaces) {
            String strUserInterfaceName = oUserInterface.getName();

            if (oListOfUserInterfaceNames.contains(strUserInterfaceName)) {
                throw new RMInputModelException(RMMessageResource.IMM_DUPLICATED_USER_INTERFACE, strUserInterfaceName, false);
            }

            String strForSA = oUserInterface.getForSystemArchitecture();
            if (getSystemArchitectureByName(strForSA) == null) {
                throw new RMInputModelException(RMMessageResource.IMM_USER_INTERFACE_REFERRING_TO_NON_EXISTING_SYSTEM_ARCHITECTURE, strUserInterfaceName, strForSA, false);
            }

            String strFromComponentInstanceName = oUserInterface.getFrom().getFromComponentInstance();
            ComponentInstanceType oComponentInstance = getComponentInstance(strForSA, strFromComponentInstanceName);
            if (oComponentInstance == null) {
                throw new RMInputModelException(RMMessageResource.IMM_USER_INTERFACE_REFERRING_TO_NON_EXISTING_COMPONENT_INSTANCE, strUserInterfaceName, strFromComponentInstanceName, strForSA, false);
            }

            String strComponentName = oComponentInstance.getForComponent();
            String strFromProvidedServiceName = oUserInterface.getFrom().getFromProvidedService();
            if (getProvidedService(strComponentName, strFromProvidedServiceName) == null) {
                throw new RMInputModelException(RMMessageResource.IMM_USER_INTERFACE_REFERRING_TO_NON_EXISTING_PROVIDED_SERVICE, strUserInterfaceName, strFromProvidedServiceName, strFromComponentInstanceName, false);
            }


            if (getUsageProfileByForUserInterface(strUserInterfaceName) == null) {
                throw new RMInputModelException(RMMessageResource.IMM_USER_INTERFACE_MISSING_USAGE_PROFILE, strUserInterfaceName, false);
            }

            verifyBranchingConditionsAndLoopingCountsAndRecursiveCalls(oUserInterface);

            oListOfUserInterfaceNames.add(strUserInterfaceName);
        }
    }

    /**
     * Get the system architecture by name
     * @param systemArchitectureName the system architecture name.
     * @return the system architecture.
     */
    public SystemArchitectureType getSystemArchitectureByName(String systemArchitectureName) {
        if (StringUtils.isBlank(systemArchitectureName)) {
            return null;
        }

        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:SystemReliabilityModel/tns:SystemArchitecture[@name='" + systemArchitectureName + "']";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (SystemArchitectureType) oXmlObjects[0];
    }

    /**
     * Get the user interface by name
     * @param userInterfaceName the user interface name.
     * @return the user interface.
     */
    public UserInterface getUserInterfaceByName(String userInterfaceName) {
        if (StringUtils.isBlank(userInterfaceName)) {
            return null;
        }

        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:SystemReliabilityModel/tns:UserInterface[@name='" + userInterfaceName + "']";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (UserInterface) oXmlObjects[0];
    }

    /**
     * Get the usage profile by "for user interface".
     * @param forUserInterface the "for user interface".
     * @return the usage profile.
     */
    public UsageProfile getUsageProfileByForUserInterface(String forUserInterface) {
        if (StringUtils.isBlank(forUserInterface)) {
            return null;
        }

        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:SystemReliabilityModel/tns:UsageProfile[@forUserInterface='" + forUserInterface + "']";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (UsageProfile) oXmlObjects[0];
    }

    /**
     * Get all the user interfaces.
     * @return all the user interfaces.
     */
    public UserInterface[] getUserInterfaces() {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:SystemReliabilityModel/tns:UserInterface";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (UserInterface[]) oXmlObjects;
    }

    /**
     * Get all the services.
     * @return all the services.
     */
    public ServiceType[] getServices() {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:ComponentReliabilitySpecification/tns:Service";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (ServiceType[]) oXmlObjects;
    }

    /**
     * Get all the system architectures.
     * @return all the system architectures.
     */
    public SystemArchitectureType[] getSystemArchitectures() {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:SystemReliabilityModel/tns:SystemArchitecture";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (SystemArchitectureType[]) oXmlObjects;
    }

    /**
     * Get all the components.
     * @return all the components.
     */
    public ComponentType[] getComponents() {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:ComponentReliabilitySpecification/tns:Component";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (ComponentType[]) oXmlObjects;
    }

    /**
     * Get all the usage profile.
     * @return all the usage profiles.
     */
    public UsageProfile[] getUsageProfiles() {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:SystemReliabilityModel/tns:UsageProfile";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (UsageProfile[]) oXmlObjects;
    }

    /**
     * Get the branching probability value for a user interface by branching label.
     * @param forUserInterface for which user interface.
     * @param branchingProbabilityLabel by which branching label.
     * @return the branching probability value.
     */
    public Double getUsageProfileProbabilityValue(String forUserInterface, String branchingProbabilityLabel) {
        Probability oProbability = getUsageProfileProbability(forUserInterface, branchingProbabilityLabel);

        return new Double(((Probability) oProbability).getValue());
    }

    /**
     * Get the branching probability for a user interface by branching label.
     * @param forUserInterface for which user interface.
     * @param branchingProbabilityLabel by which branching label.
     * @return the branching probability.
     */
    public Probability getUsageProfileProbability(String forUserInterface, String branchingProbabilityLabel) {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:SystemReliabilityModel/tns:UsageProfile[@forUserInterface='" + forUserInterface + "']/tns:Probability[@label='" + branchingProbabilityLabel + "']";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (Probability) oXmlObjects[0];
    }

    /**
     * Get the looping average value for a user interface by looping label.
     * @param forUserInterface for which user interface.
     * @param loopingAverageLabel by which looping label.
     * @return the looping average value.
     */
    public Integer getUsageProfileAverageValue(String forUserInterface, String loopingAverageLabel) {
        Average oAverage = getUsageProfileAverage(forUserInterface, loopingAverageLabel);

        return new Integer(((Average) oAverage).getValue());
    }

    /**
     * Get the looping average for a user interface by looping label.
     * @param forUserInterface for which user interface.
     * @param loopingAverageLabel by which looping label.
     * @return the looping average.
     */
    public Average getUsageProfileAverage(String forUserInterface, String loopingAverageLabel) {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:SystemReliabilityModel/tns:UsageProfile[@forUserInterface='" + forUserInterface + "']/tns:Average[@label='" + loopingAverageLabel + "']";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (Average) oXmlObjects[0];
    }

    /**
     * Get the service implementation by a user interface name.
     * @param userInterfaceName a user interface name.
     * @return the service implementation.
     */
    public ServiceImplementationType getServiceImplementationByUserInterfaceName(String userInterfaceName) {
        UserInterface oUserInterface = getUserInterfaceByName(userInterfaceName);

        ComponentInstanceType oComponentInstance = getComponentInstance(oUserInterface.getForSystemArchitecture(), oUserInterface.getFrom().getFromComponentInstance());

        return getServiceImplementation(oComponentInstance.getForComponent(), oUserInterface.getFrom().getFromProvidedService());
    }

    /**
     * Get the component connector from a given component instance and a given required service within a given system architecture.
     * @param systemArchitectureName a system architecture name.
     * @param componentInstanceName a component instance name.
     * @param requiredServiceName a required service name.
     * @return the component connector.
     */
    public ComponentConnectorType getComponentConnectorFrom(String systemArchitectureName, String componentInstanceName, String requiredServiceName) {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:SystemReliabilityModel/tns:SystemArchitecture[@name='" + systemArchitectureName + "']/tns:ComponentConnector";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        for (XmlObject oXmlObject : oXmlObjects) {
            ComponentConnectorType oComponentConnectorType = (ComponentConnectorType) oXmlObject;
            ComponentConnectorType.From oFrom = oComponentConnectorType.getFrom();
            if (StringUtils.equals(componentInstanceName, oFrom.getFromComponentInstance())
                    && StringUtils.equals(requiredServiceName, oFrom.getFromProvidedService())) {

                return oComponentConnectorType;
            }

        }

        return null;
    }

    /**
     * Get the component connector for a given component instance and a given required service within a given system architecture.
     * @param systemArchitectureName a system architecture name.
     * @param componentInstanceName a component instance name.
     * @param requiredServiceName a required service name.
     * @return the component connector.
     */
    public ComponentConnectorType getComponentConnectorFor(String systemArchitectureName, String componentInstanceName, String requiredServiceName) {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:SystemReliabilityModel/tns:SystemArchitecture[@name='" + systemArchitectureName + "']/tns:ComponentConnector";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        for (XmlObject oXmlObject : oXmlObjects) {
            ComponentConnectorType oComponentConnectorType = (ComponentConnectorType) oXmlObject;
            For oFor = oComponentConnectorType.getFor();
            if (StringUtils.equals(componentInstanceName, oFor.getForComponentInstance())
                    && StringUtils.equals(requiredServiceName, oFor.getForRequiredService())) {

                return oComponentConnectorType;
            }
        }

        return null;
    }

    /**
     * Get the component instance  by a system architecture name and a component instance name.
     * @param systemArchitectureName a system architecture name.
     * @param componentInstanceName a component instance name.
     * @return the component instance.
     */
    public ComponentInstanceType getComponentInstance(String systemArchitectureName, String componentInstanceName) {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:SystemReliabilityModel/tns:SystemArchitecture[@name='" + systemArchitectureName + "']/tns:ComponentInstance[@name='" + componentInstanceName + "']";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (ComponentInstanceType) oXmlObjects[0];
    }

    /**
     * Get the service implementation by a component name and a provided service name.
     * @param componentName a component name.
     * @param providedServiceName a provided service name.
     * @return the service implementation.
     */
    public ServiceImplementationType getServiceImplementation(String componentName, String providedServiceName) {
        String strXPath = m_namespaceDeclaration + "$this/tns:SystemReliabilityModeling/tns:ComponentReliabilitySpecification/tns:Component[@name='" + componentName + "']/tns:ServiceImplementation[@forService='" + providedServiceName + "']";

        XmlObject[] oXmlObjects = m_oXmlObject.selectPath(strXPath);
        if (oXmlObjects == null || oXmlObjects.length == 0) {
            return null;
        }

        return (ServiceImplementationType) oXmlObjects[0];
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#clone()
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    /**
     * Get all the XML errors.
     * @return all the XML errors.
     */
    public List<XmlError> getXMLErrors() {
        return m_oXMLErrors;
    }
}
